package com.qd.pay.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.core.weixinmanager.domain.OrgWxConfigDTO;
import com.cdqidi.core.weixinmanager.model.OrgWxConfig;
import com.cdqidi.core.weixinmanager.service.OrgWxConfigService;
import com.cdqidi.dto.ApiResult;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.*;
import com.jfinal.weixin.sdk.api.PaymentApi;
import com.jfinal.weixin.sdk.kit.PaymentKit;
import com.qd.pay.config.PayInfoConfig;
import com.qd.pay.domain.WxBillsDTO;
import com.qd.pay.model.WxBills;
import com.qd.pay.model.WxBillsTime;
import com.qd.pay.repository.WxBillsRepository;
import com.qd.pay.repository.WxBillsTimeRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FileUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class WxBillsService {

    private final WxBillsRepository wxBillsRepository;
    private final WxBillsTimeRepository wxBillsTimeRepository;
    private final OrgWxConfigService orgWxConfigService;

    @Resource
    private CloseableHttpClient closeableHttpClient;
    @Resource
    private PayInfoConfig payInfo;
    @Resource
    private RedisTemplateUtil<WxBills> redisTemplateUtil;

    private static final String PREFIX_ID = "wx_bills_bid_{0}";

    @Transactional(rollbackFor = Exception.class)
    public WxBills save(WxBills model) throws ApiException {
        try {
            if (!wxBillsRepository.save(model)) {
                throw new ApiException(ApiResult.error());
            }
            return model;
        } finally {
            clearCache(model);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean updateById(WxBills model) throws ApiException {
        final WxBills historyModel = Optional.ofNullable(getById(model.getBid())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        try {
            if (!wxBillsRepository.updateById(model)) {
                throw new ApiException(ApiResult.error());
            }
            return true;
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean removeById(String bid) throws ApiException {
        final WxBills historyModel = Optional.ofNullable(getById(bid)).orElseThrow(() -> new ApiException("ID不存在,不能删除"));
        try {
            if (!wxBillsRepository.removeById(bid)) {
                throw new ApiException(ApiResult.error());
            }
            return true;
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
        }
    }

    public Boolean isExists(String bid) {
        return wxBillsRepository.isExists(bid);
    }

    public WxBills getById(String bid) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, bid);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY * 3), () -> wxBillsRepository.getById(bid), WxBills.class);
    }

    public IPage<WxBillsDTO> page(Page<WxBillsDTO> page, WxBills model) {
        QueryWrapper<WxBills> queryWrapper = new QueryWrapper<>(model);
        IPage<WxBills> iPage = wxBillsRepository.page(page.convert(this::dtoToModel), queryWrapper);
        return iPage.convert(this::modelToDto);
    }


    private void clearCache(WxBills model) {
        if (null == model) {
            return;
        }
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getBid());
        redisTemplateUtil.deleteByKey(key);
    }

    public WxBillsDTO modelToDto(WxBills model) {
        if (null == model) {
            return null;
        }
        WxBillsDTO dto = new WxBillsDTO();
        dto.setBid(model.getBid());
        dto.setPaytime(model.getPaytime());
        dto.setMchid(model.getMchid());
        dto.setChildmchid(model.getChildmchid());
        dto.setDeviceid(model.getDeviceid());
        dto.setWxorderid(model.getWxorderid());
        dto.setOrderid(model.getOrderid());
        dto.setOpenid(model.getOpenid());
        dto.setTradeType(model.getTradeType());
        dto.setResultCode(model.getResultCode());
        dto.setBankType(model.getBankType());
        dto.setFeeType(model.getFeeType());
        dto.setTotalFee(model.getTotalFee());
        dto.setEnterprisebonusamount(model.getEnterprisebonusamount());
        dto.setTWxOrderid(model.getTWxOrderid());
        dto.setTOrderid(model.getTOrderid());
        dto.setTTotalFee(model.getTTotalFee());
        dto.setTEnterpriseAmount(model.getTEnterpriseAmount());
        dto.setTLx(model.getTLx());
        dto.setBody(model.getBody());
        dto.setAttach(model.getAttach());
        dto.setServicecharge(model.getServicecharge());
        dto.setRate(model.getRate());
        dto.setOrdertime(model.getOrdertime());
        dto.setWxorgid(model.getWxorgid());
        model.freeData();
        return dto;
    }

    public WxBills dtoToModel(WxBillsDTO dto) {
        if (null == dto) {
            return null;
        }
        WxBills model = new WxBills();
        model.setBid(dto.getBid());
        model.setPaytime(dto.getPaytime());
        model.setMchid(dto.getMchid());
        model.setChildmchid(dto.getChildmchid());
        model.setDeviceid(dto.getDeviceid());
        model.setWxorderid(dto.getWxorderid());
        model.setOrderid(dto.getOrderid());
        model.setOpenid(dto.getOpenid());
        model.setTradeType(dto.getTradeType());
        model.setResultCode(dto.getResultCode());
        model.setBankType(dto.getBankType());
        model.setFeeType(dto.getFeeType());
        model.setTotalFee(dto.getTotalFee());
        model.setEnterprisebonusamount(dto.getEnterprisebonusamount());
        model.setTWxOrderid(dto.getTWxOrderid());
        model.setTOrderid(dto.getTOrderid());
        model.setTTotalFee(dto.getTTotalFee());
        model.setTEnterpriseAmount(dto.getTEnterpriseAmount());
        model.setTLx(dto.getTLx());
        model.setBody(dto.getBody());
        model.setAttach(dto.getAttach());
        model.setServicecharge(dto.getServicecharge());
        model.setRate(dto.getRate());
        model.setOrdertime(dto.getOrdertime());
        model.setWxorgid(dto.getWxorgid());
        dto.freeData();
        return model;
    }

    private static final String[] WX_BILL_HEADS = {"交易时间", "公众账号ID", "商户号", "子商户号", "设备号", "微信订单号", "商户订单号", "用户标识", "交易类型", "交易状态", "付款银行", "货币种类", "总金额", "企业红包金额",
            "微信退款单号", "商户退款单号", "退款金额", "企业红包退款金额", "退款类型", "退款状态", "商品名称", "商户数据包", "手续费", "费率"};

    @Transactional(rollbackFor = Exception.class)
    public void wxBillImportDb(String file, Integer billDate, String appId) throws IOException {
        List<CSVRecord> cList = CsvUtil.readCSV(file, WX_BILL_HEADS);
        if (!cList.isEmpty()) {
            wxBillsRepository.remove(new QueryWrapper<WxBills>().eq("ordertime", billDate).eq("wxorgid", appId));
            wxBillsTimeRepository.remove(new QueryWrapper<WxBillsTime>().eq("tjsj", billDate).eq("wxorgid", appId));
            List<WxBills> wxBills = getWxBills(cList, billDate, appId);
            WxBillsTime time = getWxBillsTime(cList, appId);
            if (null != time) {
                time.setTjsj(billDate);
                wxBillsTimeRepository.save(time);
                time.freeData();
            }
            if (null != wxBills && !wxBills.isEmpty()) {
                wxBillsRepository.saveBatch(wxBills);
                wxBills.clear();
            }
        }
    }

    private List<WxBills> getWxBills(List<CSVRecord> cList, Integer billDate, String appId) {
        List<WxBills> list = null;
        if (!cList.isEmpty()) {
            if (cList.size() > 2) {
                list = new ArrayList<>();
                CSVRecord r;
                WxBills data;
                LocalDateTime date;
                String payTime;
                String totalFee;
                String enterpriseBonusAmount;
                String serviceCharge;
                String rate;
                String tTotalFee;
                String tEnterpriseAmount;
                String body;
                for (int i = 1; i < cList.size() - 2; i++) {
                    r = cList.get(i);
                    data = new WxBills();
                    payTime = replace(r.get("交易时间"));
                    if (null != payTime && !"".equals(payTime)) {
                        date = DateUtil.getDate(payTime, "yyyy-MM-dd HH:mm:ss");
                        data.setPaytime(date);
                    }
                    data.setWxorgid(appId);
                    data.setBid(UUID.randomUUID().toString());
                    data.setWxorderid(replace(r.get("公众账号ID")));
                    data.setMchid(replace(r.get("商户号")));
                    data.setChildmchid(replace(r.get("子商户号")));
                    data.setDeviceid(replace(r.get("设备号")));
                    data.setWxorderid(replace(r.get("微信订单号")));
                    data.setOrderid(replace(r.get("商户订单号")));
                    data.setOpenid(replace(r.get("用户标识")));
                    data.setTradeType(replace(r.get("交易类型")));
                    data.setResultCode(replace(r.get("交易状态")));
                    data.setBankType(replace(r.get("付款银行")));
                    data.setFeeType(replace(r.get("货币种类")));
                    totalFee = replace(r.get("总金额"));
                    if (null != totalFee && !"".equals(totalFee)) {
                        data.setTotalFee(Double.valueOf(totalFee));
                    }
                    enterpriseBonusAmount = replace(r.get("企业红包金额"));
                    if (null != enterpriseBonusAmount && !"".equals(enterpriseBonusAmount)) {
                        data.setEnterprisebonusamount(Double.valueOf(enterpriseBonusAmount));
                    }
                    data.setTWxOrderid(replace(r.get("微信退款单号")));
                    data.setTOrderid(replace(r.get("商户退款单号")));
                    tTotalFee = replace(r.get("退款金额"));
                    if (null != tTotalFee && !"".equals(tTotalFee)) {
                        data.setTTotalFee(Double.valueOf(tTotalFee));
                    }
                    tEnterpriseAmount = replace(r.get("企业红包退款金额"));
                    if (null != tEnterpriseAmount && !"".equals(tEnterpriseAmount)) {
                        data.setTEnterpriseAmount(Double.valueOf(tEnterpriseAmount));
                    }
                    data.setTLx(replace(r.get("退款类型")));
                    data.setTZt(replace(r.get("退款状态")));
                    body = replace(r.get("商品名称"));
                    data.setBody(body);
                    data.setAttach(replace(r.get("商户数据包")));
                    serviceCharge = replace(r.get("手续费"));
                    if (null != serviceCharge && !"".equals(serviceCharge)) {
                        data.setServicecharge(Double.valueOf(serviceCharge));
                    }
                    rate = replace(r.get("费率"));
                    if (null != rate && !"".equals(rate)) {
                        rate = rate.replace("%", "");
                        data.setRate(Double.valueOf(rate));
                    }
                    data.setOrdertime(billDate);
                    list.add(data);
                }
            }
        }
        return list;
    }

    private WxBillsTime getWxBillsTime(List<CSVRecord> cList, String appId) {
        CSVRecord csvRecord = cList.get(cList.size() - 1);
        String singular = replace(csvRecord.get(0));
        if (null == singular || "".equals(singular)) {
            return null;
        }
        WxBillsTime data = new WxBillsTime();
        data.setSingular(Integer.parseInt(singular));
        String totalFee = replace(csvRecord.get(1));
        if (null != totalFee && !"".equals(totalFee)) {
            data.setTotalFee(Double.valueOf(totalFee));
        }
        String refund = replace(csvRecord.get(2));
        if (null != refund && !"".equals(refund)) {
            data.setRefund(Double.valueOf(refund));
        }
        String enterpriseBonusAmount = replace(csvRecord.get(3));
        if (null != enterpriseBonusAmount && !"".equals(enterpriseBonusAmount)) {
            data.setEnterprisebonusamount(Double.valueOf(enterpriseBonusAmount));
        }
        String serviceCharge = replace(csvRecord.get(4));
        if (null != serviceCharge && !"".equals(serviceCharge)) {
            data.setServicecharge(Double.valueOf(serviceCharge));
        }
        data.setWxorgid(appId);
        return data;
    }

    public String replace(String str) {
        if (null == str || "".equals(str)) {
            return "";
        }
        return str.replace("`", "");
    }

    private List<OrgWxConfigDTO> getOrgWxConfig() {
        final List<OrgWxConfig> all = orgWxConfigService.getAll();
        return Optional.ofNullable(all).orElse(Collections.emptyList()).stream().map(orgWxConfigService::modelToDto).collect(Collectors.toList());
    }

    public void downloadBill() {
        downloadBill("", null, true);
    }

    public void downloadBill(String billDate, String appId, boolean throwEx) {
        if (!StringUtils.hasLength(billDate)) {
            billDate = LocalDate.now().plusDays(-1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        }
        List<OrgWxConfigDTO> configList = Collections.emptyList();
        if (!StringUtils.hasLength(appId)) {
            configList = getOrgWxConfig();
        } else {
            OrgWxConfigDTO config = orgWxConfigService.getByIdDto(appId);
            if (null != config) {
                configList = new ArrayList<>(1);
                configList.add(config);
            }
        }
        if (!configList.isEmpty()) {
            try {
                for (OrgWxConfigDTO orgWxconfigDto : configList) {
                    log.info("开始下载对账单,公众号名称: {},日期: {}", orgWxconfigDto.getAppName(), billDate);
                    download(orgWxconfigDto, billDate);
                }
            } catch (ApiException e) {
                if (throwEx) {
                    throw e;
                }
            }
        }
    }

    private void download(OrgWxConfigDTO appConfig, String billDate) {
        String fileSuffix = ".gzip";
        if(log.isDebugEnabled()) {
            log.debug("appid: {}", appConfig.getAppId());
            log.debug("mchid: {}", appConfig.getMchId());
            log.debug("apiKey: {}", appConfig.getApiKey());
            log.debug("bill_date: {}", billDate);
        }
        String xmlResult = createXml(appConfig.getAppId(), appConfig.getMchId(), appConfig.getApiKey(), billDate, null, true);
        try {
            byte[] b = downloadBill(closeableHttpClient, xmlResult);
            boolean zipFile = GzipUtil.isZipFile(b);
            if (!zipFile) {
                fileSuffix = ".txt";
            }
            String gzipBasePath = payInfo.getWxBillPath() + File.separator + "gzip" + File.separator;
            File file = new File(gzipBasePath);
            if (!file.exists()) {
                boolean b1 = file.mkdirs();
                if (!b1) {
                    log.info("创建文件夹失败，地址：{}", gzipBasePath);
                }
            }
            FileUtil.createFile(b, gzipBasePath, billDate + "_" + appConfig.getAppId() + fileSuffix);
            if (zipFile) {
                b = GzipUtil.unGzip(b);
                FileUtil.createFile(b, payInfo.getWxBillPath(), billDate + "_" + appConfig.getAppId() + ".csv");
            } else {
                throw new ApiException(400011, "没有对账单信息下载");
            }
        } catch (IOException e) {
            throw new ApiException(e);
        }

    }

    private byte[] downloadBill(CloseableHttpClient client, String params) throws IOException {
        byte[] bytes;
        HttpPost httpPost = new HttpPost(DOWNLOAD_BILL_URL);  // 接口
        httpPost.addHeader(HTTP.CONTENT_TYPE, "application/xml");
        StringEntity entity = new StringEntity(params);
        try {
            entity.setContentType("text/html;charset=utf-8");
            httpPost.setEntity(entity);
            HttpResponse response = client.execute(httpPost);
            HttpEntity resEntity = response.getEntity();
            try {
                try (InputStream inputStream = resEntity.getContent()) {
                    bytes = FileUtil.inputStreamToByte(inputStream);
                }
            } finally {
                EntityUtils.consume(resEntity);
            }
        } finally {
            EntityUtils.consume(entity);
        }
        return bytes;
    }

    private static final String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";

    private static String createXml(String appId, String mchId, String apiKey, String billDate, PaymentApi.BillType billType, boolean isGzip) {
        Map<String, String> params = new HashMap<>();
        params.put("appid", appId);
        params.put("mch_id", mchId);
        params.put("nonce_str", System.currentTimeMillis() + "");
        params.put("bill_date", billDate);
        if (null == billType) {
            params.put("bill_type", PaymentApi.BillType.ALL.name());
        } else {
            params.put("bill_type", billType.name());
        }
        if (isGzip) {
            params.put("tar_type", "GZIP");
        }
        String sign = PaymentKit.createSign(params, apiKey);
        params.put("sign", sign);
        return PaymentKit.toXml(params);
    }

    @Transactional(rollbackFor = Exception.class)
    public void importDb() {
        String wxBillPath = payInfo.getWxBillPath();
        File f = new File(wxBillPath);
        if (!f.exists()) {
            boolean b = f.mkdirs();
            if (!b) {
                log.info("创建文件夹失败，地址: {}", wxBillPath);
            }
        }
        Collection<File> list = FileUtils.listFiles(f, new String[]{"csv"}, false);
        log.info("执行对账单csv文件导入，数量: {}", list.size());
        if (!list.isEmpty()) {
            String fileName;
            String appId;
            String[] fName;
            for (File file : list) {
                try {
                    fName = file.getName().split("_");
                    fileName = fName[0];
                    appId = fName[1];
                    long start = System.currentTimeMillis();
                    log.info("开始导入，文件名： " + file.getName());
                    wxBillImportDb(file.getPath(), Integer.parseInt(fileName), appId);
                    long end = System.currentTimeMillis();
                    log.info("完成导入，文件名： " + file.getName() + "，用时: " + (end - start) / 1000 + "秒,删除文件: " + Files.deleteIfExists(file.toPath()));
                } catch (IOException e) {
                    log.error("下载对账单", e);
                }
            }
        }
    }
}